/**
 * @license
 * Copyright 2018 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Gulp script to build Blockly for Node & NPM.
 * Run this script by calling "npm install" in this directory.
 */

var gulp = require('gulp');
gulp.shell = require('gulp-shell');
gulp.concat = require('gulp-concat');
gulp.replace = require('gulp-replace');
gulp.rename = require('gulp-rename');
gulp.insert = require('gulp-insert');
gulp.umd = require('gulp-umd');

var path = require('path');
var fs = require('fs');
var rimraf = require('rimraf');
var execSync = require('child_process').execSync;
var through2 = require('through2');

var closureCompiler = require('google-closure-compiler').gulp();
var closureDeps = require('google-closure-deps');
var packageJson = require('./package.json');
var argv = require('yargs').argv;

const upstream_url = "https://github.com/google/blockly.git";

////////////////////////////////////////////////////////////
//                        Build                           //
////////////////////////////////////////////////////////////

const licenseRegex = `\\/\\*\\*
 \\* @license
 \\* (Copyright \\d+ (Google LLC|Massachusetts Institute of Technology))
( \\* All rights reserved.
)? \\*
 \\* Licensed under the Apache License, Version 2.0 \\(the "License"\\);
 \\* you may not use this file except in compliance with the License.
 \\* You may obtain a copy of the License at
 \\*
 \\*   http://www.apache.org/licenses/LICENSE-2.0
 \\*
 \\* Unless required by applicable law or agreed to in writing, software
 \\* distributed under the License is distributed on an "AS IS" BASIS,
 \\* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 \\* See the License for the specific language governing permissions and
 \\* limitations under the License.
 \\*\\/`;

/**
 * Helper method for stripping the Google's and MIT's Apache Licenses.
 */
function stripApacheLicense() {
  // Strip out Google's and MIT's Apache licences.
  // Closure Compiler preserves dozens of Apache licences in the Blockly code.
  // Remove these if they belong to Google or MIT.
  // MIT's permission to do this is logged in Blockly issue #2412.
  return gulp.replace(new RegExp(licenseRegex, "g"), '');
}

/**
 * Helper method for prepending the auto-generated header text.
 */
function prependHeader() {
  return gulp.insert.prepend(`// Do not edit this file; automatically generated by gulp.\n`);
}

/**
 * Closure compiler warning groups used to treat warnings as errors.
 * For a full list of closure compiler groups, consult:
 * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/DiagnosticGroups.java#L113
 */
var JSCOMP_ERROR = [
  'accessControls',
  'checkPrototypalTypes',
  'checkRegExp',
  'checkTypes',
  'checkVars',
  'conformanceViolations',
  'const',
  'constantProperty',
  'deprecated',
  'deprecatedAnnotations',
  'duplicateMessage',
  'es5Strict',
  'externsValidation',
  'functionParams',
  'globalThis',
  'invalidCasts',
  'misplacedTypeAnnotation',
  'missingGetCssName',
  // 'missingOverride',
  'missingPolyfill',
  'missingProperties',
  'missingProvide',
  'missingRequire',
  'missingReturn',
  // 'missingSourcesWarnings',
  'moduleLoad',
  'msgDescriptions',
  'nonStandardJsDocs',
  // 'polymer',
  // 'reportUnknownTypes',
  // 'strictCheckTypes',
  // 'strictMissingProperties',
  'strictModuleDepCheck',
  // 'strictPrimitiveOperators',
  'suspiciousCode',
  'typeInvalidation',
  'undefinedNames',
  'undefinedVars',
  'underscore',
  'unknownDefines',
  'unusedLocalVariables',
  // 'unusedPrivateMembers',
  'useOfGoogBase',
  'uselessCode',
  'untranspilableFeatures',
  'visibility'
];

/**
 * Helper method for calling the Closure compiler.
 * @param {*} compilerOptions
 * @param {boolean=} opt_verbose Optional option for verbose logging
 * @param {boolean=} opt_warnings_as_error Optional option for treating warnings
 *     as errors.
 */
function compile(compilerOptions, opt_verbose, opt_warnings_as_error) {
  compilerOptions = compilerOptions || {};
  compilerOptions.compilation_level = 'SIMPLE_OPTIMIZATIONS';
  compilerOptions.warning_level = opt_verbose ? 'VERBOSE' : 'DEFAULT';
  compilerOptions.language_in =
    compilerOptions.language_in || 'ECMASCRIPT5_STRICT';
  compilerOptions.language_out = 'ECMASCRIPT5_STRICT';
  compilerOptions.rewrite_polyfills = false;
  compilerOptions.hide_warnings_for = 'node_modules';
  if (opt_warnings_as_error) {
    compilerOptions.jscomp_error = JSCOMP_ERROR;
  }

  const platform = ['native', 'java', 'javascript'];

  return closureCompiler(compilerOptions, { platform });
}

/**
 * Helper method for possibly adding the closure library into a sources array.
 * @param {Array.<string>} srcs 
 */
function maybeAddClosureLibrary(srcs) {
  if (argv.closureLibrary) {
    // If you require the google closure library, you can include it in your
    // build by adding the --closure-library flag.
    // You will also need to include the "google-closure-library" in your list
    // of devDependencies.
    console.log('Including the google-closure-library in your build.');
    if (!fs.existsSync('./node_modules/google-closure-library')) {
      throw Error('You must add the google-closure-library to your ' +
        'devDependencies in package.json, and run `npm install`.');
    }
    srcs.push('./node_modules/google-closure-library/closure/goog/**/**/*.js');
  }
  return srcs;
}

/**
 * This task builds Blockly's core files.
 *     blockly_compressed.js
 */
gulp.task('build-compressed', function (cb) {
  const defines = 'Blockly.VERSION="' + packageJson.version + '"';
  return gulp.src(maybeAddClosureLibrary(['core/**/**/*.js']), {base: './'})
    // Directories in Blockly are used to group similar files together
    // but are not used to limit access with @package, instead the
    // method means something is internal to Blockly and not a public
    // API.
    // Flatten all files so they're in the same directory, but ensure that
    // files with the same name don't conflict.
    .pipe(gulp.rename(function (p) {
      var dirname = p.dirname.replace(new RegExp(path.sep, "g"), "-");
      p.dirname = "";
      p.basename = dirname + "-" + p.basename;
    }))
    .pipe(stripApacheLicense())
    .pipe(compile({
      dependency_mode: 'PRUNE',
      entry_point: './core-requires.js',
      js_output_file: 'blockly_compressed.js',
      externs: ['./externs/svg-externs.js', './externs/goog-externs.js'],
      define: defines,
      language_in:
        argv.closureLibrary ? 'ECMASCRIPT_2015' : 'ECMASCRIPT5_STRICT'
    }, argv.verbose, argv.strict))
    .pipe(prependHeader())
    .pipe(gulp.dest('./'));
});

/**
 * This task builds the Blockly's built in blocks.
 *     blocks_compressed.js
 */
gulp.task('build-blocks', function () {
  // Add provides used throughout blocks/ in order to be compatible with the
  // compiler.  Anything added to this list must be removed from the compiled
  // result using the remove regex steps below.
  const provides = `
goog.provide('Blockly');
goog.provide('Blockly.Blocks');
goog.provide('Blockly.Comment');
goog.provide('Blockly.FieldCheckbox');
goog.provide('Blockly.FieldColour');
goog.provide('Blockly.FieldDropdown');
goog.provide('Blockly.FieldImage');
goog.provide('Blockly.FieldLabel');
goog.provide('Blockly.FieldMultilineInput');
goog.provide('Blockly.FieldNumber');
goog.provide('Blockly.FieldTextInput');
goog.provide('Blockly.FieldVariable');
goog.provide('Blockly.Mutator');
goog.provide('Blockly.Warning');`;
  return gulp.src(maybeAddClosureLibrary(['blocks/*.js']), {base: './'})
    // Add Blockly.Blocks to be compatible with the compiler.
    .pipe(gulp.replace(`goog.provide('Blockly.Constants.Colour');`,
      `${provides}goog.provide('Blockly.Constants.Colour');`))
    .pipe(stripApacheLicense())
    .pipe(compile({
      dependency_mode: 'NONE',
      externs: ['./externs/goog-externs.js'],
      js_output_file: 'blocks_compressed.js'
    }, argv.verbose, argv.strict))
    .pipe(gulp.replace('\'use strict\';', '\'use strict\';\n\n\n'))
    // Remove Blockly.Blocks to be compatible with Blockly.
    .pipe(gulp.replace(/var Blockly=\{[^;]*\};\n?/, ''))
    // Remove Blockly Fields to be compatible with Blockly.
    .pipe(gulp.replace(/Blockly\.Field[^=\(]+=\{[^;]*\};/g, ''))
    // Remove Blockly Warning, Comment & Mutator to be compatible with Blockly.
    .pipe(gulp.replace(/Blockly\.(Comment|Warning|Mutator)=\{[^;]*\};/g, ''))
    .pipe(prependHeader())
    .pipe(gulp.dest('./'));
});

/**
 * A helper method for building a Blockly code generator.
 * @param {string} language Generator language.
 * @param {string} namespace Language namespace.
 */
function buildGenerator(language, namespace) {
  var provides = `
goog.provide('Blockly.Generator');
goog.provide('Blockly.utils.global');
goog.provide('Blockly.utils.string');`;
  return gulp.src([`generators/${language}.js`, `generators/${language}/*.js`], {base: './'})
    .pipe(stripApacheLicense())
    // Add Blockly.Generator and Blockly.utils.string to be compatible with the compiler.
    .pipe(gulp.replace(`goog.provide('Blockly.${namespace}');`,
      `${provides}goog.provide('Blockly.${namespace}');`))
    .pipe(compile({
      dependency_mode: 'NONE',
      externs: ['./externs/goog-externs.js'],
      js_output_file: `${language}_compressed.js`
    }, argv.verbose, argv.strict))
    .pipe(gulp.replace('\'use strict\';', '\'use strict\';\n\n\n'))
    // Remove Blockly.Generator and Blockly.utils.string to be compatible with Blockly.
    .pipe(gulp.replace(/var Blockly=\{[^;]*\};\s*Blockly.utils.global={};\s*Blockly.utils.string={};\n?/, ''))
    .pipe(prependHeader())
    .pipe(gulp.dest('./'));
};

/**
 * This task builds the javascript generator.
 *     javascript_compressed.js
 */
gulp.task('build-javascript', function() {
  return buildGenerator('javascript', 'JavaScript');
});

/**
 * This task builds the python generator.
 *     python_compressed.js
 */
gulp.task('build-python', function() {
  return buildGenerator('python', 'Python');
});

/**
 * This task builds the php generator.
 *     php_compressed.js
 */
gulp.task('build-php', function() {
  return buildGenerator('php', 'PHP');
});

/**
 * This task builds the lua generator.
 *     lua_compressed.js
 */
gulp.task('build-lua', function() {
  return buildGenerator('lua', 'Lua');
});

/**
 * This task builds the dart generator:
 *     dart_compressed.js
 */
gulp.task('build-dart', function() {
  return buildGenerator('dart', 'Dart');
});

/**
 * This tasks builds all the generators:
 *     javascript_compressed.js
 *     python_compressed.js
 *     php_compressed.js
 *     lua_compressed.js
 *     dart_compressed.js
 */
gulp.task('build-generators', gulp.parallel(
  'build-javascript',
  'build-python',
  'build-php',
  'build-lua',
  'build-dart'
));

/**
 * This task builds Blockly's uncompressed file.
 *     blockly_uncompressed.js
 */
gulp.task('build-uncompressed', function() {
  const closurePath = argv.closureLibrary ?
    'node_modules/google-closure-library/closure/goog' :
    'closure/goog';
  const header = `// Do not edit this file; automatically generated by gulp.
'use strict';

this.IS_NODE_JS = !!(typeof module !== 'undefined' && module.exports);

this.BLOCKLY_DIR = (function(root) {
  if (!root.IS_NODE_JS) {
    // Find name of current directory.
    var scripts = document.getElementsByTagName('script');
    var re = new RegExp('(.+)[\\\/]blockly_(.*)uncompressed\\\.js$');
    for (var i = 0, script; script = scripts[i]; i++) {
      var match = re.exec(script.src);
      if (match) {
        return match[1];
      }
    }
    alert('Could not detect Blockly\\'s directory name.');
  }
  return '';
})(this);

this.BLOCKLY_BOOT = function(root) {
  // Execute after Closure has loaded.
`;
  const footer = `
delete root.BLOCKLY_DIR;
delete root.BLOCKLY_BOOT;
delete root.IS_NODE_JS;
};

if (this.IS_NODE_JS) {
  this.BLOCKLY_BOOT(this);
  module.exports = Blockly;
} else {
  document.write('<script src="' + this.BLOCKLY_DIR +
      '/${closurePath}/base.js"></script>');
  document.write('<script>this.BLOCKLY_BOOT(this);</script>');
}
`;

let deps = [];
return gulp.src(maybeAddClosureLibrary(['core/**/**/*.js']))
  .pipe(through2.obj((file, _enc, cb) => {
    const result = closureDeps.parser.parseFile(file.path);
    for (const dep of result.dependencies) {
      deps.push(dep);
    }
    cb(null);
  }))
  .on('end', () => {
    // Update the path to closure for any files that we don't know the full path
    // of (parsed from a goog.addDependency call).
    for (const dep of deps) {
      dep.setClosurePath(closurePath);
    }

    const addDependency = closureDeps.depFile.getDepFileText(closurePath, deps);

    const requires = `goog.addDependency("base.js", [], []);

// Load Blockly.
goog.require('Blockly.requires')
`;
    fs.writeFileSync('blockly_uncompressed.js',
      header +
      addDependency +
      requires +
      footer);
  });
});

/**
 * This task builds Blockly's lang files.
 *     msg/*.js
 */
gulp.task('build-langfiles', function(done) {
  // Run js_to_json.py
  const jsToJsonCmd = `python ./i18n/js_to_json.py \
--input_file ${path.join('msg', 'messages.js')} \
--output_dir ${path.join('msg', 'json')} \
--quiet`;
  execSync(jsToJsonCmd, { stdio: 'inherit' });

  // Run create_messages.py
  let json_files = fs.readdirSync(path.join('msg', 'json'));
  json_files = json_files.filter(file => file.endsWith('json') &&
    !(new RegExp(/(keys|synonyms|qqq|constants)\.json$/).test(file)));
  json_files = json_files.map(file => path.join('msg', 'json', file));
  const createMessagesCmd = `python ./i18n/create_messages.py \
  --source_lang_file ${path.join('msg', 'json', 'en.json')} \
  --source_synonym_file ${path.join('msg', 'json', 'synonyms.json')} \
  --source_constants_file ${path.join('msg', 'json', 'constants.json')} \
  --key_file ${path.join('msg', 'json', 'keys.json')} \
  --output_dir ${path.join('msg', 'js')} \
  --quiet ${json_files.join(' ')}`;
    execSync(createMessagesCmd, { stdio: 'inherit' });

  done();
});

/**
 * This tasks builds Blockly's core files:
 *     blockly_compressed.js
 *     blocks_compressed.js
 *     blockly_uncompressed.js
 */
gulp.task('build-core', gulp.parallel(
  'build-compressed',
  'build-blocks',
  'build-uncompressed'
));

/**
 * This task builds all of Blockly:
 *     blockly_compressed.js
 *     blocks_compressed.js
 *     javascript_compressed.js
 *     python_compressed.js
 *     php_compressed.js
 *     lua_compressed.js
 *     dart_compressed.js
 *     blockly_uncompressed.js
 *     msg/json/*.js
 */
gulp.task('build', gulp.parallel(
  'build-core',
  'build-generators',
  'build-langfiles'
));

////////////////////////////////////////////////////////////
//                        Typings                         //
////////////////////////////////////////////////////////////

// Generates the TypeScript definition file (d.ts) for Blockly.
// As well as generating the typings of each of the files under core/ and msg/,
// the script also pulls in a number of part files from typings/parts.
// This includes the header (incl License), additional useful interfaces
// including Blockly Options and Google Closure typings.
gulp.task('typings', function (cb) {
  const tmpDir = './typings/tmp';
  const blocklySrcs = [
    "core/",
    "core/components",
    "core/components/tree",
    "core/components/menu",
    "core/keyboard_nav",
    "core/renderers/common",
    "core/renderers/measurables",
    "core/theme",
    "core/utils",
    "msg/"
  ];
  // Clean directory if exists.
  if (fs.existsSync(tmpDir)) {
    rimraf.sync(tmpDir);
  }
  fs.mkdirSync(tmpDir);

  // Find all files that will be included in the typings file.
  let files = [];
  blocklySrcs.forEach((src) => {
    files = files.concat(fs.readdirSync(src)
      .filter(fn => fn.endsWith('.js'))
      .map(fn => path.join(src, fn)));
  });

  // Generate typings file for each file.
  files.forEach((file) => {
    const typescriptFileName = `${path.join(tmpDir, file)}.d.ts`;
    if (file.indexOf('core/msg.js') > -1) {
      return;
    }
    const cmd = `node ./node_modules/typescript-closure-tools/definition-generator/src/main.js ${file} ${typescriptFileName}`;
    console.log(`Generating typings for ${file}`);
    execSync(cmd, { stdio: 'inherit' });
  });

  const srcs = [
    'typings/parts/blockly-header.d.ts',
    'typings/parts/blockly-interfaces.d.ts',
    `${tmpDir}/core/**`,
    `${tmpDir}/core/components/**`,
    `${tmpDir}/core/components/tree/**`,
    `${tmpDir}/core/components/menu/**`,
    `${tmpDir}/core/keyboard_nav/**`,
    `${tmpDir}/core/renderers/common/**`,
    `${tmpDir}/core/renderers/measurables/**`,
    `${tmpDir}/core/utils/**`,
    `${tmpDir}/core/theme/**`,
    `${tmpDir}/msg/**`
  ];
  return gulp.src(srcs)
    .pipe(gulp.concat('blockly.d.ts'))
    .pipe(gulp.dest('typings'))
    .on('end', function () {
      // Clean up tmp directory.
      if (fs.existsSync(tmpDir)) {
        rimraf.sync(tmpDir);
      }
    });
});

////////////////////////////////////////////////////////////
//                  NPM packaging tasks                   //
////////////////////////////////////////////////////////////

// The destination path where all the NPM distribution files will go.
const packageDistribution = './dist';

/**
 * A helper method for wrapping a file into a Universal Module Definition.
 * @param {string} namespace The export namespace.
 * @param {Array<Object>} dependencies An array of dependencies to inject.
 */
function packageUMD(namespace, dependencies) {
  return gulp.umd({
    dependencies: function () { return dependencies; },
    namespace: function () { return namespace; },
    exports: function () { return namespace; },
    template: path.join(__dirname, 'package/templates/umd.template')
  });
};

/**
 * A helper method for wrapping a file into a CommonJS module for Node.js.
 * @param {string} namespace The export namespace.
 * @param {Array<Object>} dependencies An array of dependencies to inject.
 */
function packageCommonJS(namespace, dependencies) {
  return gulp.umd({
    dependencies: function () { return dependencies; },
    namespace: function () { return namespace; },
    exports: function () { return namespace; },
    template: path.join(__dirname, 'package/templates/node.template')
  });
};

/**
 * This task wraps blockly_compressed.js into a UMD module.
 * @example import 'blockly/blockly';
 */
gulp.task('package-blockly', function() {
  return gulp.src('blockly_compressed.js')
    .pipe(packageUMD('Blockly', []))
    .pipe(gulp.rename('blockly.js'))
    .pipe(gulp.dest(packageDistribution));
});

/**
 * This task wraps blocks_compressed.js into a CommonJS module for Node.js.
 * This is an equivelant task to package-blockly but for Node.js.
 * @example import 'blockly/blockly-node';
 */
gulp.task('package-blockly-node', function() {
  // Override textToDomDocument, providing a Node.js alternative to DOMParser.
  return gulp.src('blockly_compressed.js')
    .pipe(gulp.insert.append(`
      if (typeof DOMParser !== 'function') {
        var DOMParser = require("jsdom/lib/jsdom/living").DOMParser;
        var XMLSerializer = require("jsdom/lib/jsdom/living").XMLSerializer;
        var doc = Blockly.utils.xml.textToDomDocument(
          '<xml xmlns="https://developers.google.com/blockly/xml"></xml>');
        Blockly.utils.xml.document = function() {
          return doc;
        };
      }`))
    .pipe(packageCommonJS('Blockly', []))
    .pipe(gulp.rename('blockly-node.js'))
    .pipe(gulp.dest(packageDistribution));
})

/**
 * This task wraps blocks_compressed.js into a UMD module.
 * @example import 'blockly/blocks';
 */
gulp.task('package-blocks', function() {
  return gulp.src('blocks_compressed.js')
    .pipe(gulp.insert.prepend(`
    Blockly.Blocks={};`))
    .pipe(packageUMD('Blockly.Blocks', [{
        name: 'Blockly',
        amd: './core',
        cjs: './core',
      }]))
    .pipe(gulp.rename('blocks.js'))
    .pipe(gulp.dest(packageDistribution));
});

/**
 * This task wraps package/index.js into a UMD module.
 * We implicitly require the Node entry point in CommonJS environments,
 * and the Browser entry point for AMD environments.
 * @example import * as Blockly from 'blockly';
 */
gulp.task('package-index', function() {
  return gulp.src('package/index.js')
    .pipe(packageUMD('Blockly', [{
        name: 'Blockly',
        amd: './browser',
        cjs: './node',
      }]))
    .pipe(gulp.rename('index.js'))
    .pipe(gulp.dest(packageDistribution));
});

/**
 * This task wraps package/browser/index.js into a UMD module.
 * By default, the module includes Blockly core and built-in blocks,
 * as well as the JavaScript code generator and the English block
 * localization files.
 * This module is configured (in package.json) to replaces the module
 * built by package-node in browser environments.
 * @example import * as Blockly from 'blockly/browser';
 */
gulp.task('package-browser', function() {
  return gulp.src('package/browser/index.js')
    .pipe(packageUMD('Blockly', [{
        name: 'Blockly',
        amd: './core-browser',
        cjs: './core-browser',
      },{
        name: 'En',
        amd: './msg/en',
        cjs: './msg/en',
      },{
        name: 'BlocklyBlocks',
        amd: './blocks',
        cjs: './blocks',
      },{
        name: 'BlocklyJS',
        amd: './javascript',
        cjs: './javascript',
      }]))
    .pipe(gulp.rename('browser.js'))
    .pipe(gulp.dest(packageDistribution));
});

/**
 * This task wraps package/browser/core.js into a UMD module.
 * By default, the module includes the Blockly core package and a
 * helper method to set the locale.
 * This module is configured (in package.json) to replaces the module
 * built by package-node-core in browser environments.
 * @example import * as Blockly from 'blockly/core';
 */
gulp.task('package-core', function() {
  return gulp.src('package/browser/core.js')
    .pipe(packageUMD('Blockly', [{
        name: 'Blockly',
        amd: './blockly',
        cjs: './blockly',
      }]))
    .pipe(gulp.rename('core-browser.js'))
    .pipe(gulp.dest(packageDistribution));
});

/**
 * This task wraps package/node/index.js into a CommonJS module for Node.js.
 * By default, the module includes Blockly core and built-in blocks,
 * as well as all the code generators and the English block localization files.
 * This module is configured (in package.json) to be replaced by the module
 * built by package-browser in browser environments.
 * @example import * as Blockly from 'blockly/node';
 */
gulp.task('package-node', function() {
  return gulp.src('package/node/index.js')
    .pipe(packageCommonJS('Blockly', [{
        name: 'Blockly',
        cjs: './core',
      },{
        name: 'En',
        cjs: './msg/en',
      },{
        name: 'BlocklyBlocks',
        cjs: './blocks',
      },{
        name: 'BlocklyJS',
        cjs: './javascript',
      },{
        name: 'BlocklyPython',
        cjs: './python',
      },{
        name: 'BlocklyPHP',
        cjs: './php',
      },{
        name: 'BlocklyLua',
        cjs: './lua',
      }, {
        name: 'BlocklyDart',
        cjs: './dart',
      }]))
    .pipe(gulp.rename('node.js'))
    .pipe(gulp.dest(packageDistribution));
});

/**
 * This task wraps package/node/core.js into a CommonJS module for Node.js.
 * By default, the module includes the Blockly core package for Node.js
 * and a helper method to set the locale.
 * This module is configured (in package.json) to be replaced by the module
 * built by package-core in browser environments.
 * @example import * as Blockly from 'blockly/core';
 */
gulp.task('package-node-core', function() {
  return gulp.src('package/node/core.js')
    .pipe(packageCommonJS('Blockly', [{
        name: 'Blockly',
        amd: './blockly-node',
        cjs: './blockly-node',
      }]))
    .pipe(gulp.rename('core.js'))
    .pipe(gulp.dest(packageDistribution));
});

/**
 * A helper method for packaging a Blockly code generator into a UMD module.
 * @param {string} file Source file name.
 * @param {string} rename Destination file name.
 * @param {string} generator Generator export namespace.
 */
function packageGenerator(file, rename, generator) {
  return gulp.src(file)
    .pipe(packageUMD(generator, [{
        name: 'Blockly',
        amd: './core',
        cjs: './core',
      }]))
    .pipe(gulp.rename(rename))
    .pipe(gulp.dest(packageDistribution));
};

/**
 * This task wraps javascript_compressed.js into a UMD module.
 * @example import 'blockly/javascript';
 */
gulp.task('package-javascript', function() {
  return packageGenerator('javascript_compressed.js', 'javascript.js', 'Blockly.JavaScript');
});

/**
 * This task wraps python_compressed.js into a UMD module.
 * @example import 'blockly/python';
 */
gulp.task('package-python', function() {
  return packageGenerator('python_compressed.js', 'python.js', 'Blockly.Python');
});

/**
 * This task wraps lua_compressed.js into a UMD module.
 * @example import 'blockly/lua';
 */
gulp.task('package-lua', function() {
  return packageGenerator('lua_compressed.js', 'lua.js', 'Blockly.Lua');
});

/**
 * This task wraps dart_compressed.js into a UMD module.
 * @example import 'blockly/dart';
 */
gulp.task('package-dart', function() {
  return packageGenerator('dart_compressed.js', 'dart.js', 'Blockly.Dart');
});

/**
 * This task wraps php_compressed.js into a UMD module.
 * @example import 'blockly/php';
 */
gulp.task('package-php', function() {
  return packageGenerator('php_compressed.js', 'php.js', 'Blockly.PHP');
});

/**
 * This task wraps each of the msg/js/* files into a UMD module.
 * @example import * as En from 'blockly/msg/en';
 */
gulp.task('package-locales', function() {
  // Remove references to goog.provide and goog.require.
  return gulp.src('msg/js/*.js')
      .pipe(gulp.replace(/goog\.[^\n]+/g, ''))
      .pipe(gulp.insert.prepend(`
      var Blockly = {};Blockly.Msg={};`))
      .pipe(packageUMD('Blockly.Msg', [{
          name: 'Blockly',
          amd: '../core',
          cjs: '../core',
        }]))
      .pipe(gulp.dest(`${packageDistribution}/msg`));
});

/**
 * This task creates a UMD bundle of Blockly which includes the Blockly
 * core files, the built-in blocks, the JavaScript code generator and the
 * English localization files.
 * @example <script src="https://unpkg.com/blockly/blockly.min.js"></script>
 */
gulp.task('package-umd-bundle', function() {
  var srcs = [
    'blockly_compressed.js',
    'msg/js/en.js',
    'blocks_compressed.js',
    'javascript_compressed.js'
  ];
  return gulp.src(srcs)
    .pipe(gulp.concat('blockly.min.js'))
    .pipe(packageUMD('Blockly', []))
    .pipe(gulp.dest(`${packageDistribution}`))
});

/**
 * This task copies all the media/* files into the distribution directory.
 */
gulp.task('package-media', function() {
  return gulp.src('./media/*')
    .pipe(gulp.dest(`${packageDistribution}/media`));
});

/**
 * This task copies the package.json file into the distribution directory.
 */
gulp.task('package-json', function() {
  return gulp.src('./package.json')
    .pipe(gulp.dest(`${packageDistribution}`))
});

/**
 * This task copies the package/README.md file into the distribution directory.
 * This file is what developers will see at https://www.npmjs.com/package/blockly.
 */
gulp.task('package-readme', function() {
  return gulp.src('./package/README.md')
    .pipe(gulp.dest(`${packageDistribution}`))
});

/**
 * This task copies the typings/blockly.d.ts TypeScript definition file into the
 * distribution directory.
 * The bundled declaration file is referenced in package.json in the types property.
 */
gulp.task('package-dts', function() {
  return gulp.src('./typings/blockly.d.ts')
    .pipe(gulp.dest(`${packageDistribution}`))
});

/**
 * This task prepares the NPM distribution files under the /dist directory.
 */
gulp.task('package', gulp.parallel(
  'package-index',
  'package-browser',
  'package-node',
  'package-core',
  'package-node-core',
  'package-blockly',
  'package-blockly-node',
  'package-blocks',
  'package-javascript',
  'package-python',
  'package-lua',
  'package-dart',
  'package-php',
  'package-locales',
  'package-media',
  'package-umd-bundle',
  'package-json',
  'package-readme',
  'package-dts'
  ));

// The default task builds Blockly.
gulp.task('default', gulp.series(['build']));


// Stash current state, check out the named branch, and sync with
// google/blockly.
function syncBranch(branchName) {
  return function(done) {
    execSync('git stash save -m "Stash for sync"', { stdio: 'inherit' });
    execSync('git checkout ' + branchName, { stdio: 'inherit' });
    execSync('git pull ' + upstream_url + ' ' + branchName,
        { stdio: 'inherit' });
    execSync('git push origin ' + branchName, { stdio: 'inherit' });
    done();
  }
}

// Stash current state, check out develop, and sync with google/blockly.
gulp.task('git-sync-develop', syncBranch('develop'));

// Stash current state, check out master, and sync with google/blockly.
gulp.task('git-sync-master', syncBranch('master'));

// Helper function: get a name for a rebuild branch. Format: rebuild_mm_dd_yyyy.
function getRebuildBranchName() {
  var date = new Date();
  var mm = date.getMonth() + 1; // Month, 0-11
  var dd = date.getDate(); // Day of the month, 1-31
  var yyyy = date.getFullYear();
  return 'rebuild_' + mm + '_' + dd + '_' + yyyy;
};

// Helper function: get a name for a rebuild branch. Format: rebuild_yyyy_mm.
function getRCBranchName() {
  var date = new Date();
  var mm = date.getMonth() + 1; // Month, 0-11
  var yyyy = date.getFullYear();
  return 'rc_' + yyyy + '_' + mm;
};

// Recompile and push to origin.
gulp.task('git-recompile', gulp.series([
    'git-sync-develop',
    function(done) {
      var branchName = getRebuildBranchName();
      console.log('make-rebuild-branch: creating branch ' + branchName);
      execSync('git checkout -b ' + branchName, { stdio: 'inherit' });
      done();
    },
    'build',
    'typings',
    function(done) {
      console.log('push-rebuild-branch: committing rebuild');
      execSync('git commit -am "Rebuild"', { stdio: 'inherit' });
      var branchName = getRebuildBranchName();
      execSync('git push origin ' + branchName, { stdio: 'inherit' });
      console.log('Branch ' + branchName + ' pushed to GitHub.');
      console.log('Next step: create a pull request against develop.');
      done();
    }
  ])
);

// Create and push an RC branch.
// Note that this pushes to google/blockly.
gulp.task('git-create-rc', gulp.series([
    'git-sync-develop',
    function(done) {
      var branchName = getRCBranchName();
      execSync('git checkout -b ' + branchName, { stdio: 'inherit' });
      execSync('git push ' + upstream_url + ' ' + branchName,
          { stdio: 'inherit' });
      execSync('git checkout -b gh-pages');
      execSync('git push ' + upstream_url + ' gh-pages');
      done();
    },
  ])
);

// See https://docs.npmjs.com/cli/version.
gulp.task('preversion', gulp.series([
    'git-sync-master',
    function(done) {
      // Create a branch named bump_version for the bump and rebuild.
      execSync('git checkout -b bump_version', { stdio: 'inherit' });
      done();
    },
  ])
);

// See https://docs.npmjs.com/cli/version
gulp.task('postversion', gulp.series([
  function(done) {
      // Push both the branch and tag to google/blockly.
      execSync('git push ' + upstream_url + ' bump_version',
          { stdio: 'inherit' });
      var tagName = 'v' + packageJson.version;
      execSync('git push ' + upstream_url + ' ' + tagName,
          { stdio: 'inherit' });
      done();
    }
  ])
);
